package Vim::Complete;

use strict;
use warnings;
use PPI;
use File::Find;
use File::Spec;


our $VERSION = '0.01';


use base qw(Class::Accessor::Complex);


__PACKAGE__
    ->mk_new
    ->mk_scalar_accessors(qw(min_length))
    ->mk_array_accessors(qw(dirs))
    ->mk_hash_accessors(qw(result))
    ->mk_boolean_accessors(qw(verbose));


sub gather {
    my ($self, $filename) = @_;

    # does PPI not like relative filenames?
    $filename = File::Spec->rel2abs($filename);

    my $document = PPI::Document->new($filename);

    unless (UNIVERSAL::isa($document, 'PPI::Document')) {
        warn "couldn't parse $filename\n";
        return;
    }

    # get a hash reference so we can change the hash in place
    my $result = $self->result;

    my $min_length = $self->min_length || 3;

    $result->{$_} = 1 for
        grep { /\w{$min_length,}/ }
        map  { $_->namespace }
        @{ $document->find('PPI::Statement::Package') || [] };

    $result->{$_} = 1 for
        grep { /\w{$min_length,}/ }
        map  { substr($_, 0, 1, ''); $_ }
        map  { $_->variables }
        @{ $document->find('PPI::Statement::Variable') || [] };

    $result->{$_} = 1 for
        grep { /\w{$min_length,}/ }
        map  { $_->name }
        @{ $document->find('PPI::Statement::Sub') || [] };
}


sub report {
    my $self = shift;
    my @result = sort $self->result_keys;
    wantarray ? @result : \@result;
}

sub report_to_file {
    my ($self, $filename) = @_;
    die "no filename\n" unless defined $filename;
    open my $fh, '>', $filename or die "can't open $filename for writing: $!\n";
    print $fh "$_\n" for $self->report;
    close $fh or die "can't close $fh: $!\n";
}


sub parse {
    my $self = shift;
    my $verbose = $self->verbose;
    find(sub {
        return unless -f && /\.pm$/;
        warn "processing $File::Find::name\n" if $verbose;
        $self->gather($_);
    }, $self->dirs);

    $self; # for chaining
}


1;


__END__

{% USE p = PodGenerated %}

=head1 NAME

{% p.package %} - Generate autocompletion information for vim

=head1 SYNOPSIS

    Vim::Complete->new(
        dirs       => \@dirs,
        verbose    => $verbose,
        min_length => $min_length,
    )->parse->report_to_file($filename);

=head1 DESCRIPTION

Vim has a good autocompletion mechanism. In insert mode, you can type Contrl-n
to complete on the current string; you can cycle through the possible
completions by repeatedly typing Control-n. See C<:help complete> in vim for
more information.

By default, vim completes on identifiers it finds in the current buffer,
buffers in other windows, other loaded buffers, unloaded buffers, tags and
included files. That means you still have to type the identifier once so
vim knows about it.

However, you can extend the way vim completes. It can take additional
identifiers from a file. So Vim::Complete takes a list of directories -
usually C<@INC> -, looks at the modules contained therein, parses package
names, variable names and subroutine names and writes them to a file.

Now you need to tell vim where to find the file with the Perl identifiers. Put
this line into your C<.vimrc>:

    set complete+=k~/.vimcomplete

The <+=k> tells vim to also look into the specified file.

For this to work well, you need to tell vim that colons are part of
identifiers in Perl (for example, C<Foo::Bar> is an identifier. Put this line
in your C<.vimrc>:

    set iskeyword+=:

Included in this distribution is the program C<mk_vim_complete>, which is a
command-line frontend to Vim::Complete.

You can tell Vim::Complete to only use identifiers that are of a certain
minimum length. An identifier that is only one character long (such as C<$x>)
doesn't need to be completed. If you would include two-character identifiers,
you might throw off the autocompletion by having to cycle through too many
identifiers. So the default minimum length is 3.

{% p.write_inheritance %}

=head1 METHODS

=over 4

{% p.write_methods %}

=item parse

Assumes that C<dir()>, and optionally C<verbose()> and C<min_length()>, have
been set and starts to look in the directories for files ending in C<.pm>. For
each file it gathers information using C<gather()>.

Returns the Vim::Complete object so method calls can be chained as seen in the
L</SYNOPSIS>.

=item report

Takes all the gathered findings and returns the list of identifiers. Returns
an array in list context, or a reference to the array in scalar context.

=item report_to_file

Takes as argument a filename. Writes the report generated by C<report()> to
the file.

=item gather

Takes a filename of a module, parses the source code and makes a note of the
package names, subroutine names and variable names it sees.

This method is called by C<parse()>; it is unlikely that you want to call it
yourself.

=back

{% PROCESS standard_pod %}

=cut

